package jrandom.model;

import jrandom.JRandomPlugin;
import jrandom.algorithm.AdaptiveRandomStrategy;
import jrandom.algorithm.AlgorithmStrategy;
import jrandom.algorithm.RandomStrategy;
import jrandom.report.XMLReport;
import jrandom.wizards.NewJRandomTestCaseWizard;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.wizards.newresource.BasicNewResourceWizard;

public class TestCase implements IRunnableWithProgress {
	
	private Class<?> c;
	private IMethod iMethod;
	private Parameter[] parameters = new Parameter[0];
	private Class<?>[] paraTypes = new Class<?>[0];
	private String fileName;
	private int dataNum;
	
	private boolean isART = true;
	private boolean isXML = true;
	
	private AlgorithmStrategy algorithm;
	private Object[][] testData;
	private Object[] testResult;
	
	private NewJRandomTestCaseWizard jWizard;
	
	public TestCase(NewJRandomTestCaseWizard jWizard, IMethod iMethod, String[] mins, String[] maxs, int num) {
		this.iMethod = iMethod;
		this.dataNum = num;
		this.jWizard = jWizard;
		init(iMethod, mins, maxs);
	}

	@Override
	public void run(IProgressMonitor monitor) throws InvocationTargetException,
			InterruptedException {
		try {
			doTestCase(monitor);
		} catch (CoreException e) {
			throw new InvocationTargetException(e);
		} finally {
			monitor.done();
		}
	}

	private void init(IMethod iMethod, String[] mins, String[] maxs) {
		try {
			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
			IResource resource = root.findMember(iMethod.getJavaProject().getOutputLocation());
			
			URL[] urls = { new URL(resource.getLocationURI().toString()+"/") };
			ClassLoader loader = new URLClassLoader(urls); 

			c = loader.loadClass(iMethod.getDeclaringType().getFullyQualifiedName('$')); 
			
			String[] paraList = iMethod.getParameterTypes();
			if (paraList.length != 0) {
				parameters = new Parameter[paraList.length];
				paraTypes = new Class<?>[paraList.length];
				for (int i = 0; i < paraList.length; i++) {
					if (Signature.getTypeSignatureKind(paraList[i]) == Signature.BASE_TYPE_SIGNATURE) {
						if (Signature.toString(paraList[i]).equals("int")) {
							paraTypes[i] = int.class;
						} else if (Signature.toString(paraList[i]).equals("char")) {
							paraTypes[i] = char.class;
						} else if (Signature.toString(paraList[i]).equals("double")) {
							paraTypes[i] = double.class;
						} else {
							paraTypes[i] = Object.class;
						}
					} else {
						paraTypes[i] = Class.forName("java.lang.String");
					}
					parameters[i] = new Parameter(paraTypes[i], iMethod.getParameterNames()[i], mins[i], maxs[i]);
				}
			}
			
		} catch (ClassNotFoundException e) {
			
			e.printStackTrace();
		} catch (SecurityException e) {
			
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
	}

	private void doTestCase(IProgressMonitor monitor) throws CoreException {
		
		// task to main test
		monitor.beginTask("Testing...", 3);
		mainTest();
		monitor.worked(1);
		
		// task to create the output file
		monitor.setTaskName("Create " + fileName);
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		IResource resource = root.findMember(iMethod.getJavaProject().getPath());
		if (!resource.exists() || !(resource instanceof IContainer)) {
			throwCoreException("Container \"" + iMethod.getJavaProject().getElementName() + "\" does not exist.");
		}
		IContainer container = (IContainer) resource;
		final IFile file = container.getFile(new Path(fileName));
		try {
			
//			XMLReport report = new XMLReport(this);
			InputStream stream = writeFile();
			
			if (file.exists()) {
				file.setContents(stream, true, true, monitor);
			} else {
				file.create(stream, true, monitor);
			}
			stream.close();
		} catch (IOException e) {
		}
		
		monitor.worked(1);
		
		// task to open the file
		monitor.setTaskName("Opening file for editing...");
		jWizard.getShell().getDisplay().asyncExec(new Runnable() {
			public void run() {
				IWorkbenchPage page =
					PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
				try {
					BasicNewResourceWizard.selectAndReveal(file, jWizard.getWorkbench().getActiveWorkbenchWindow());
					IDE.openEditor(page, file, true);
				} catch (PartInitException e) {
				}
			}
		});
		monitor.worked(1);
	}
	
	private InputStream writeFile() {
		String str = "";
		for (int i = 0; i < dataNum; i++) {
			for (int j = 0; j < testData.length; j++) {
				str = str.concat(testData[j][i].toString()+"\t\t");
			}
			str = str.concat(testResult[i].toString()+"\n");
		}
		
		return new ByteArrayInputStream(str.getBytes());
	}

	private void mainTest() {
		
		if (isART) {
			algorithm = new AdaptiveRandomStrategy(dataNum, parameters);
		} else {
			algorithm = new RandomStrategy(dataNum, parameters);
		}
		
		
		try {
			if (iMethod.isConstructor()) {
				constructorTest();
			} else if (Flags.isStatic(iMethod.getFlags())) {
				staticTest();
			} else if (Flags.isPublic(iMethod.getFlags())) {
				publicTest();
			} else {
				throw new Exception();
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void constructorTest() {
	
		try {
			Constructor<?> ctor = c.getConstructor(paraTypes);
			
			testData = algorithm.getValueFromFactory();
			testResult = new Object[dataNum];
			for (int i = 0; i < dataNum; i++) {
				Object[] oneCase = new Object[parameters.length];
				for (int j = 0; j < oneCase.length; j++) {
					oneCase[j] = testData[j][i];
				}
				testResult[i] = ctor.newInstance(oneCase);
			}
			
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	private void staticTest() {
		try {
			Method method = c.getMethod(iMethod.getElementName(), paraTypes);
			testData = algorithm.getValueFromFactory();
			testResult = new Object[dataNum];
			for (int i = 0; i < dataNum; i++) {
				Object[] oneCase = new Object[parameters.length];
				for (int j = 0; j < oneCase.length; j++) {
					oneCase[j] = testData[j][i];
				}
				testResult[i] = method.invoke(null, oneCase);
			}
		} catch (SecurityException e1) {
			e1.printStackTrace();
		} catch (NoSuchMethodException e1) {
			e1.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	private void publicTest() {
		
		try {
			Method method = c.getMethod(iMethod.getElementName(), paraTypes);
			Constructor<?>[] ctors = c.getConstructors();
			if (ctors.length == 0) {
				return;
			}
			
			Class<?>[] paraList = ctors[0].getParameterTypes();
			Parameter[] args = new Parameter[paraList.length];
			for (int i = 0; i < paraList.length; i++) {
				args[i] = new Parameter(paraList[i], paraList[i].getName());
			}
			AlgorithmStrategy forAnInstance = new RandomStrategy(1, args);
			Object[][] ctorsArgs = forAnInstance.getValueFromFactory();
			
			Object[] oneCase = new Object[paraList.length];
			for (int i = 0; i < oneCase.length; i++) {
				oneCase[i] = ctorsArgs[i][0];
			}
			Object obj = ctors[0].newInstance(oneCase);
			
			testData = algorithm.getValueFromFactory();
			testResult = new Object[dataNum];
			for (int i = 0; i < dataNum; i++) {
				oneCase = new Object[parameters.length];
				for (int j = 0; j < oneCase.length; j++) {
					oneCase[j] = testData[j][i];
				}
				
				try {
					testResult[i] = method.invoke(obj, oneCase);
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}
	}

	private void throwCoreException(String message) throws CoreException {
		IStatus status =
			new Status(IStatus.ERROR, JRandomPlugin.PLUGIN_ID, IStatus.OK, message, null);
		throw new CoreException(status);
	}
	
	public void setOutputFile(String fileName, boolean isXML) {
		this.fileName = fileName;
		this.isXML = isXML;
	}

	public void setARTorRT(boolean isART) {
		this.isART = isART;
	}

	public AlgorithmStrategy getAlgorithm() {
		return algorithm;
	}

	public String getFileName() {
		return fileName;
	}

	public String getMethodName() {
		return iMethod.getElementName();
	}

	public String getDescription() {
		return null;
	}

	public Parameter[] getParaList() {
		return parameters;
	}

	public Object[][] getTestData() {
		return testData;
	}

	public Object[] getTestResult() {
		return testResult;
	}
	

	
}
